Intermediate Python

List comprehensions

The last thing we will cover in this workshop is a little different to the other parts of this session. Rather than being a technique for making your code more easily sharable, reliable or user-friendly, it's instead a language feature which can make your life easier.

There's a common pattern that you will see come up a lot in programming in general and and Python in particular and that is "loop-append". We've done it already in the morse code exercises. Let's look at a section of encode first:

morse = []

for letter in message:
    letter = letter.lower()
    morse_letter = letter_to_morse[letter]
    morse.append(morse_letter)

morse_message = " ".join(morse)

The code inside the function is doing a few different things. First it creates an empty list, knowing full-well that it's about to be filled up. The next three lines then loop over some input container (message) and use some result of that to iteratively append to the end of morse.

for loops are useful when you want to do something for each item in a list but what we're doing here is taking one container and transforming it into something new. The purpose of this code is to create the new container, not to use the old one.

This is such a common pattern that Python has special built-in syntax for doing this loop-append in one go called list comprehension. Taking a simpler example for now of an input of a range function and the output being the square of the inputs:

In [1]:
s = []
for i in range(4):
    s.append(i*i)
s
Out[1]:
[0, 1, 4, 9]

We end up outputting $0^2$, $1^2$, $2^2$ and $3^2$. The equivalent with a list comprehension would be:

In [2]:
[i*i for i in range(4)]
Out[2]:
[0, 1, 4, 9]

You can see that it has output exactly the same answer but was one in one line instead of four. You might recognise some familiar features in there but they're not in the usual order. Let's break it apart, step-by-step:

We start with the outer square brackets. They're doing a similar things here to when you create a normal list like [1, 2, 3] but in this situation, due to what's inside they work a little differently but the result is still a normal Python list:

↓                     ↓
[i*i for i in range(4)]

The next part is the input for the comprehension. This syntax is taken from the normal for loop syntax and works in the same way. We're saying that we want to loop over range(4) and each time round the loop, we will use the variable i to refer to the current value:

              ↓
[i*i for i in range(4)]

The last part is where it differs. Rather than the body of the loop going on a new line after a colon, the body goes before the for keyword. You can only have a single expression here, not multiple lines of code. Also, there's no need for an explicit append call. Whatever is written in that place will be added into the list as it is created:

  ↓
[i*i for i in range(4)]

List comprehensions are powerful because they allow you to do a lot in a single line but that can be an issue of you try to do too much and it becomes very noisy. I would suggest that you try to keep your list comprehensions not much more complicated than the example we saw above.

Exercise

  • Adapt encode and decode in the morse module to use list comprehensions for doing the conversion. answer